#!/usr/bin/env python
"""Map verifier.

This will check that the given map has a location for every link to
connect to, that no links are specified multiple times, and that all
links are symmetric.
"""
import ConfigParser
import collections
import sys


ALL_TYPES = set(('port', 'water', 'empty', 'center', 'neutral'))


_notset = object()
class ConfigDefaultParser(ConfigParser.RawConfigParser):
    """Subclass of RawConfigParser that adds support for default values.

    >>> import StringIO
    >>> p = ConfigDefaultParser()
    >>> p.readfp(StringIO.StringIO('[stuff]\\nfoo=bar'))
    >>> p.get('stuff', 'foo')
    'bar'
    >>> p.get('stuff', 'qux', default='xyzzy')
    'xyzzy'
    >>> p.get('stuff', 'moo')
    Traceback (most recent call last):
    ...
    NoOptionError: No option 'moo' in section: 'stuff'
    """
    def get(self, section, key, default=_notset):
        """Get a value for key from section, optionally with a default.
        """
        try:
            return ConfigParser.RawConfigParser.get(self, section, key)
        except ConfigParser.NoOptionError:
            if default is _notset:
                raise
            return default


def neighbors(p, region, types=ALL_TYPES):
    neighb = []
    for s in p.get(region, 'neighbors', '').split():
        if p.get(s, 'type' '') in types:
            neighb.append(s)
    return neighb


def validate_file(f):
    """Validate the map referenced by file-like object f.

    Returns a list of errors encountered.
    """
    p = ConfigDefaultParser()
    p.readfp(f)
    have = set(p.sections())
    need = collections.defaultdict(lambda: 0)
    connections = collections.defaultdict(lambda: 0)
    ports = collections.defaultdict(lambda: [])
    errors = []
    for h in have:
        try:
            links = neighbors(p, h)
        except ConfigParser.NoSectionError, e:
            errors.append("Reigon %s has undefined neighbor %s" % (h, e.section))
            continue

        if len(links) != len(set(links)):
            errors.append('duplicate link in %s' % h)
        if p.get(h, 'type', '') not in ALL_TYPES:
            errors.append('%s needs valid type' % h)
        if not p.get(h, 'name', ''):
            errors.append('%s needs name' % h)
        if p.get(h, 'type', '') in  ['center', 'empty']:
            for l in links:
                if p.get(l, 'type', '') == 'water':
                    ports[h] += []
                    break
        if p.get(h, 'type', '') == 'port':
            land = p.get(h, 'land', '')
            if not land:
                errors.append('port %s needs land specified', h)
            ports[land] += links
            # skip symmetry check for ports
            continue
        for n in links:
            if n in have:
                connections[':'.join(sorted([h, n]))] += 1
                continue
            need[n] += 1
    for port, links in ports.items():
        port_links = set([l for l in links if p.get(l, 'type', '') == 'water'])
        if p.get(port, 'port', '') == 'auto':
            if port_links:
                errors.append('%s has ports but was auto' % port)
        else:
            water_links = []
            try:
                water_links = set(neighbors(p, port, ['water']))
            except ConfigParser.NoSectionError:
                errors.append('%s had bad links - not checking ports' % port)
                continue
            if not port_links or port_links != water_links:
                errors.append('%s has bad port links' % port)
            # validate land links are vaguely reasonable
            all_neighbors = neighbors(p, port)
            for land in [l for l in links if p.get(l, 'type', '') != 'water']:
                if land not in all_neighbors:
                    errors.append('a port for %s has invalid land link %s'
                                  % (port, land))
    errors += ['%s %d' % (y, x) for x, y in
               sorted((v, k) for k, v in need.items())]

    asymmetric = ' '.join(repr(k) for k, v in connections.items() if v != 2)
    if asymmetric:
        errors.append('asymmetric connections: %s' % asymmetric)
    return errors


def main(args=sys.argv):
    if len(args) != 2:
        print 'Usage: %s file/to/check' % args[0]
        return 1
    f = open(args[1])
    errors = validate_file(f)
    f.close()
    if errors:
        print 'Errors encountered:'
        print '\n'.join(errors)

    return int(not errors)

if __name__ == '__main__':
    sys.exit(main(sys.argv))
